ポート番号ごとに違うコンテンツを表示するAWS PrivateLinkを設定してみた
1つのEC2 (Webサーバー) にバーチャルホストを設定してPrivateLinkでアクセスする検証を行ったのでブログに残します。
環境
以下構成図になります。
左側のサブネットで起動しているEC2 (コンテンツ確認用) から80番ポートでVPCエンドポイントへアクセスした場合は80番ポート用のコンテンツ、8080番でアクセスした場合は8080番用のコンテンツを表示するように設定を行います。
設定
AWSリソース作成
AWSリソースはCloudFormationで作成します。
まずはVPCエンドポイント以外の部分を以下のCloudFormationテンプレートで作成します。
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09" Description: PrivateLink Metadata: # ------------------------------------------------------------# # Metadata # ------------------------------------------------------------# AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Parameters for VPC Parameters: - VPCCIDR1 - VPCCIDR2 - Label: default: Parameters for Subnet Parameters: - PublicSubnet01CIDR - PublicSubnet02CIDR - Label: default: Parameters for EC2 Parameters: - EC2VolumeSize - EC2VolumeIOPS - EC2AMI - EC2InstanceType Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# VPCCIDR1: Default: 10.0.0.0/16 Type: String VPCCIDR2: Default: 10.1.0.0/16 Type: String PublicSubnet01CIDR: Default: 10.0.0.0/24 Type: String PublicSubnet02CIDR: Default: 10.1.0.0/24 Type: String EC2VolumeSize: Default: 32 Type: Number EC2VolumeIOPS: Default: 3000 Type: Number EC2AMI: Default: ami-067871d950411e643 Type: AWS::EC2::Image::Id EC2InstanceType: Default: t3.micro Type: String Resources: # ------------------------------------------------------------# # VPC # ------------------------------------------------------------# VPC1: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VPCCIDR1 EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: private-link-service-vpc VPC2: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VPCCIDR2 EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: private-link-client-vpc # ------------------------------------------------------------# # Subnet # ------------------------------------------------------------# PublicSubnet01: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a CidrBlock: !Ref PublicSubnet01CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: private-link-service-subnet VpcId: !Ref VPC1 PublicSubnet02: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a CidrBlock: !Ref PublicSubnet02CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: private-link-client-subnet VpcId: !Ref VPC2 # ------------------------------------------------------------# # InternetGateway # ------------------------------------------------------------# InternetGateway1: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: private-link-service-igw InternetGatewayAttachment1: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway1 VpcId: !Ref VPC1 InternetGateway2: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: private-link-client-igw InternetGatewayAttachment2: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway2 VpcId: !Ref VPC2 # ------------------------------------------------------------# # RouteTable # ------------------------------------------------------------# PublicRouteTable1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC1 Tags: - Key: Name Value: private-link-service-rtb PublicRouteTableRoute1: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway1 RouteTableId: !Ref PublicRouteTable1 PublicRtAssociation1: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable1 SubnetId: !Ref PublicSubnet01 PublicRouteTable2: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC2 Tags: - Key: Name Value: private-link-client-rtb PublicRouteTableRoute2: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway2 RouteTableId: !Ref PublicRouteTable2 PublicRtAssociation2: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable2 SubnetId: !Ref PublicSubnet02 # ------------------------------------------------------------# # Security Group # ------------------------------------------------------------# ServiceEC2SG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for EC2 Web GroupName: EC2-web-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - FromPort: 80 IpProtocol: tcp ToPort: 80 CidrIp: !Ref VPCCIDR1 - FromPort: 8080 IpProtocol: tcp ToPort: 8080 CidrIp: !Ref VPCCIDR1 Tags: - Key: Name Value: EC2-web-sg VpcId: !Ref VPC1 ClientEC2SG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for EC2 Client GroupName: EC2-client-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 Tags: - Key: Name Value: EC2-client-sg VpcId: !Ref VPC2 PrivateLinkSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for private link GroupName: privatelink-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - SourceSecurityGroupId: !Ref ClientEC2SG FromPort: 80 IpProtocol: tcp ToPort: 80 - SourceSecurityGroupId: !Ref ClientEC2SG FromPort: 8080 IpProtocol: tcp ToPort: 8080 Tags: - Key: Name Value: privatelink-sg VpcId: !Ref VPC2 # ------------------------------------------------------------# # IAM # ------------------------------------------------------------# EC2IAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - 'sts:AssumeRole' ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore RoleName: iam-role-ec2 Tags: - Key: Name Value: iam-role-ec2 EC2IAMInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: iam-instanceprofile-ec2 Roles: - !Ref EC2IAMRole # ------------------------------------------------------------# # EC2 # ------------------------------------------------------------# ServiceEC2: Type: AWS::EC2::Instance Properties: BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: DeleteOnTermination: true Encrypted: true Iops: !Ref EC2VolumeIOPS VolumeSize: !Ref EC2VolumeSize VolumeType: gp3 DisableApiTermination: false IamInstanceProfile: !Ref EC2IAMInstanceProfile ImageId: !Ref EC2AMI InstanceType: !Ref EC2InstanceType NetworkInterfaces: - DeleteOnTermination: true DeviceIndex: 0 GroupSet: - !Ref ServiceEC2SG SubnetId: !Ref PublicSubnet01 Tags: - Key: Name Value: private-link-service-ec2 UserData: !Base64 | #!/bin/bash dnf install httpd -y systemctl start httpd systemctl enable httpd echo "Private Link Port 80" > /var/www/html/index.html mkdir /var/www/port8080 echo "Private Link Port 8080" > /var/www/port8080/index.html touch /etc/httpd/conf.d/port8080.conf echo "Listen 8080" > /etc/httpd/conf.d/port8080.conf echo "<VirtualHost *:8080>" >> /etc/httpd/conf.d/port8080.conf echo " DocumentRoot /var/www/port8080" >> /etc/httpd/conf.d/port8080.conf echo "</VirtualHost>" >> /etc/httpd/conf.d/port8080.conf systemctl reload httpd ClientEC2: Type: AWS::EC2::Instance Properties: BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: DeleteOnTermination: true Encrypted: true Iops: !Ref EC2VolumeIOPS VolumeSize: !Ref EC2VolumeSize VolumeType: gp3 DisableApiTermination: false IamInstanceProfile: !Ref EC2IAMInstanceProfile ImageId: !Ref EC2AMI InstanceType: !Ref EC2InstanceType NetworkInterfaces: - DeleteOnTermination: true DeviceIndex: 0 GroupSet: - !Ref ClientEC2SG SubnetId: !Ref PublicSubnet02 Tags: - Key: Name Value: private-link-client-ec2 # ------------------------------------------------------------# # NLB # ------------------------------------------------------------# NLB: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: IpAddressType: ipv4 LoadBalancerAttributes: - Key: deletion_protection.enabled Value: false Name: private-link-service-nlb Scheme: internal Subnets: - !Ref PublicSubnet01 Tags: - Key: Name Value: private-link-service-nlb Type: network TargetGroup1: Type: AWS::ElasticLoadBalancingV2::TargetGroup DependsOn: - ServiceEC2 Properties: HealthCheckEnabled: true HealthCheckIntervalSeconds: 30 HealthCheckPort: traffic-port HealthCheckProtocol: TCP HealthyThresholdCount: 5 IpAddressType: ipv4 Name: private-link-service-nlb-tg-80 Port: 80 Protocol: TCP TargetGroupAttributes: - Key: preserve_client_ip.enabled Value: true Targets: - Id: !Ref ServiceEC2 Port: 80 TargetType: instance VpcId: !Ref VPC1 TargetGroup2: Type: AWS::ElasticLoadBalancingV2::TargetGroup DependsOn: - ServiceEC2 Properties: HealthCheckEnabled: true HealthCheckIntervalSeconds: 30 HealthCheckPort: traffic-port HealthCheckProtocol: TCP HealthyThresholdCount: 5 IpAddressType: ipv4 Name: private-link-service-nlb-tg-8080 Port: 8080 Protocol: TCP TargetGroupAttributes: - Key: preserve_client_ip.enabled Value: true Targets: - Id: !Ref ServiceEC2 Port: 8080 TargetType: instance VpcId: !Ref VPC1 Listener1: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: !Ref TargetGroup1 Type: forward LoadBalancerArn: !Ref NLB Port: 80 Protocol: TCP Listener2: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: !Ref TargetGroup2 Type: forward LoadBalancerArn: !Ref NLB Port: 8080 Protocol: TCP # ------------------------------------------------------------# # VPC Endpoint Service # ------------------------------------------------------------# VPCEndpointService: DependsOn: - NLB Type: AWS::EC2::VPCEndpointService Properties: AcceptanceRequired: true NetworkLoadBalancerArns: - !Ref NLB Outputs: # ------------------------------------------------------------# # Outputs # ------------------------------------------------------------# VPC2ID: Value: !Ref VPC2 Export: Name: VPC2ID PrivateLinkSGID: Value: !Ref PrivateLinkSG Export: Name: PrivateLinkSGID PublicSubnet02ID: Value: !Ref PublicSubnet02 Export: Name: PublicSubnet02ID
上記CloudFormationテンプレートではVPCエンドポイント以外の部分が作成されます。
369~433行目のターゲットグループ、リスナールールで80番ポート、8080番ポートを振り分ける設定を入れています。
デプロイは以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --capabilities CAPABILITY_NAMED_IAM
デプロイが完了したらVPCエンドポイントを作成していきます。
以下のCloudFormationテンプレートで作成します。
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09" Description: Client Stack Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# ServiceName: Type: String Resources: # ------------------------------------------------------------# # VPC Endpoint # ------------------------------------------------------------# PrivateLink: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: false ServiceName: !Ref ServiceName VpcId: !ImportValue VPC2ID SubnetIds: - !ImportValue PublicSubnet02ID SecurityGroupIds: - !ImportValue PrivateLinkSGID
上記CloudFormationテンプレートではVPCエンドポイントを作成しています。
パラメータにあるServiceNameはマネジメントコンソールからVPC>エンドポイントサービスの詳細から確認が可能です。
デプロイは以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=ServiceName,ParameterValue=VPCエンドポイントサービス名
デプロイが完了したらマネジメントコンソールからVPC>エンドポイントサービス>該当のVPCエンドポイントサービス選択>エンドポイント接続>エンドポイント接続リクエストの承諾でリクエストの承諾を行います。
しばらくすると「状態」がPendingからAvailableになります。
ここまででリソースの作成が完了です。
動作確認
以下の手順でSession Managerを使用してEC2 (private-link-client-ec2) へ接続します。
セッションを開始する (Amazon EC2 コンソール)
接続ができたら以下のコマンドで80番ポートと8080番ポートにアクセスします。
curl http://VPCエンドポイントDNS名:80 curl http://VPCエンドポイントDNS名:8080
成功すると80番ポートの時は「Private Link Port 80」、8080番ポートの時は「Private Link Port 8080」とレスポンスが返ってきます。
さいごに
PrivateLinkの後ろにいるのがNLBなのでポート番号での振り分けは可能だろうと思いやってみました。
NLBのターゲットにALBが設定可能なのでURLでの振り分けができるのか今度検証してみたいと思いました。